﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Lucene.Linq.Storage;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;

namespace Lucene.Linq.Azure
{
    public class AzureBlobEntityStorage<T>
        : IEntityStorage<T>
        where T : IEntity, new()
    {
        private readonly CloudStorageAccount _account;
        private readonly CloudBlobClient _blobStorage;
        private readonly CloudBlobContainer _container;
        private readonly EntitySerializer<T> _serializer;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="account">Blob storage account</param>
        /// <param name="container">container name</param>
        /// <param name="retryPolicy">Retry policy</param>
        public AzureBlobEntityStorage(CloudStorageAccount account, string container, RetryPolicy retryPolicy = null)
        {
            _account = account;
            _blobStorage = _account.CreateCloudBlobClient();
            _blobStorage.RetryPolicy = retryPolicy ?? RetryPolicies.Retry(4, TimeSpan.Zero);
            _container = _blobStorage.GetContainerReference(container);
            _container.CreateIfNotExist();
            _serializer = new EntitySerializer<T>();
            Trace.TraceInformation("Instance of {0} created", GetType().ToString());
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            // No need to dispose of anything
        }

        /// <summary>
        /// Puts an object in storage
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public IEntity GetEntity(string key)
        {
            Trace.TraceInformation("Getting entity {0}", key);
            return Get(key);
        }

        /// <summary>
        /// Puts an object in storage
        /// </summary>
        /// <param name="entity">entity to store</param>
        /// <returns>true if succesfull</returns>
        public bool Put(IEntity entity)
        {
            Trace.TraceInformation("Putting entity {0}", entity.Key);
            return Put(entity is T ? (T)entity : default(T));
        }

        /// <summary>
        /// Checks if the entity exists on the storage medium
        /// </summary>
        /// <param name="key">The Key to use to verify if the entity exists</param>
        /// <returns>True if the entity exists</returns>
        /// <remarks>
        /// This call should not use the index to perform a lookup
        /// </remarks>
        public bool Exists(string key)
        {
            CloudBlob _blob = _container.GetBlobReference(Filename(key));
            bool exists = _blob.Exists();
            Trace.TraceInformation("Check if entity exists, {0}", exists);
            return exists;
        }

        /// <summary>
        /// Deletes the entity if it exists on the storage medium
        /// </summary>
        /// <param name="key">The key of the entity to delete</param>
        /// <returns>True if the Key does not, or no longer exists as a stored entity on the storage medium</returns>
        public bool Delete(string key)
        {
            try
            {
                CloudBlob _blob = _container.GetBlobReference(Filename(key));
                _blob.DeleteIfExists();
                Trace.TraceInformation("Deleted {0} if it exists", key);
                return true;
            }
            catch (Exception ex)
            {
                Trace.TraceError("Attempting to delete entity {0} failed with exception {1}", key, ex.ToString());
                return false;
            }
        }

        /// <summary>
        /// Retrieve all entity names that are currently in the storage mechanism
        /// </summary>
        /// <returns></returns>
        public IEnumerable<string> All()
        {
            IEnumerable<IListBlobItem> blobs = _container.ListBlobs(
                new BlobRequestOptions
                    {
                        UseFlatBlobListing = true,
                        BlobListingDetails = BlobListingDetails.Metadata
                    }).OfType<CloudBlob>();
            Trace.TraceInformation("Returning all entities {0}", GetType().ToString());
            return blobs.Select(blob => blob as CloudBlob).Select(cloudy => cloudy.Metadata["Name"]);
        }

        /// <summary>
        /// Gets an entity from storage
        /// </summary>
        /// <param name="key">Entity key</param>
        /// <returns></returns>
        public T Get(string key)
        {
            CloudBlob _blob = _container.GetBlobReference(Filename(key));
            byte[] data = _blob.DownloadByteArray();
            Trace.TraceInformation("Got entity {0} from storage", key);
            return _serializer.Deserialize(data);
        }

        /// <summary>
        /// Puts an entity in storage
        /// </summary>
        /// <param name="entity">Entity to store</param>
        /// <returns>true if succesfull</returns>
        public bool Put(T entity)
        {
            try
            {
                CloudBlob _blob = _container.GetBlobReference(Filename(entity.Key));
                _blob.DeleteIfExists();
                _blob.Metadata.Add("Name", Filename(entity.Key));
                _blob.Metadata.Add("Key", entity.Key);
                _blob.UploadByteArray(_serializer.Serialize(entity));
                Trace.TraceInformation("Put entity {0} to storage", entity.Key);
                return true;
            }
            catch (Exception ex)
            {
                Trace.TraceError("Tried to put entity {0} to storage, failed with exception {1}", entity.Key, ex.ToString());
                return false;
            }
        }

        private static string Filename(string key)
        {
            return string.Format("{0}.bin", key);
        }
    }
}
